import { EventClass, EventData } from "@lib/alen-helper/node/baseClass";
import { LibUniqueID } from "@lib/alen-helper/node/uniqueID";
import { OperatorManager } from "./operators/operatorManager";
import { app } from "electron";
import { concatMap, from, first, Subject, catchError, tap, map, of, Subscription } from "rxjs";
import { AppAisleServer } from "@lib/alen-helper/base/appAisle/appAisleServer";
import { Application } from "./application/application";
import { ProcessServerDrive } from "@lib/alen-helper/node/appAisle/serverDrives/process.server.drive";
import { ChildProcess, fork } from "child_process";
import { buildBaseCmdHandlers } from "./controllers/baseCmdHandlers";
import { TrayController } from "./controllers/tray/tray.controller";
import { TrayCmdHandlers } from "./controllers/tray/tray.cmdHandlers";
import { WindowController } from "./controllers/window/window.controller";
import { WindowCmdHandlers } from "./controllers/window/window.cmdHandlers";
import { AppAisle } from "@lib/alen-helper/node/appAisle";
import { get_back_observable } from "@lib/alen-helper/base/fun";

export class RunTime extends EventClass<{
    application: Application,
    mianAisle: AppAisle,
}> {

    readonly operatorManager: OperatorManager;

    private _controllers!: {
        window: WindowController,
        tray: TrayController,
    };

    private _handlers!: {
        window: WindowCmdHandlers,
        tray: TrayCmdHandlers,
    }

    get trayController() {
        return this._controllers.tray;
    }

    get windowController() {
        return this._controllers.window;
    }

    constructor(

    ) {
        super('RunTime');
        const managerOptions: Partial<OperatorManager.Options> = {
            timeout: 5000,
        }
        this.operatorManager = this._newClass(OperatorManager, this, managerOptions);
    }

    private _initController() {
        this._controllers = {
            window: this._newClass(WindowController, this),
            tray: this._newClass(TrayController, this),
        };

        this._handlers = {
            window: new WindowCmdHandlers({}, this),
            tray: new TrayCmdHandlers({}, this),
        };
    }


    private _ser: AppAisleServer<Application.CmdData> | null = null;

    private _childProcess$ = new Subject<{ id: string, childProcess: ChildProcess }>();
    private _subs: Subscription | null = null;
    private _unSub() {
        if (this._subs) this._subs.unsubscribe();
        this._subs = null;
    }

    private _sub() {
        this._unSub();
        if (!this._ser) return;
        this._subs = this._ser.event('aisleActivate')
            .subscribe(e => {
                this.triggerEvent('mianAisle', e);
            });
    }

    private _initSer() {
        const drive = this._newClass(ProcessServerDrive, {
            childProcess: () => this._childProcess$,
        });
        this._ser = this._newClass(AppAisleServer, drive);
        this._ser.setHandler<Runtime.Controller.ComonnCmdData>(this._comonnHandler);
        for (let name of Object.keys(this._handlers)) {
            this._ser.setHandler<Runtime.Controller.Window.CmdData>(this._handlers[name as 'window'])
            const handlerData = buildBaseCmdHandlers(name as 'window', {}, this, this._controllers[name as 'window']);
            this._ser.setHandler<Runtime.Controller.Window.CmdData>(handlerData.handler);
        }
        this._sub();
    }

    private _comonnHandlerGetApp = (aisle: AppAisle<AppAisle.Cmds>) => {
        const app = this.getApp(aisle);
        if (!app || !app.id) throw this._err('未初始化', 'app 未初始化');
        return app;
    }

    private _comonnHandler: AppAisle.CmdHandlers<Runtime.Controller.ComonnCmdData> = {
        app_ready: (data, appAisle) => {
            const app = this._comonnHandlerGetApp(appAisle);
            return true;
        },

        app_close: (data, appAisle) => {
            const app = this._comonnHandlerGetApp(appAisle);
            return app.close();
        },

        app_message: (data, appAisle) => {
            const app = this._comonnHandlerGetApp(appAisle);
            const source = { appid: app.id!, id: data.data.param.windowId };
            const t = this.getApp(data.data.param.appid);
            if (!t) return false;
            const target = { appid: t.id!, id: data.data.param.id }
            return t.message({ source, target, data: data.data.param.data });
        },
    }

    init() {
        app.on('window-all-closed', () => {
            //
        })
        return from(app.whenReady())
            .pipe(concatMap(() => {
                this._initController();
                this._initSer();
                if (!this._ser) throw this._err('未初始化');
                return this._ser.listen('')
            }));
    }

    addMianProcess(id: string, childProcess: ChildProcess) {
        setTimeout(() => {
            this._childProcess$.next({ id, childProcess });
        }, 0);
    }

    sendEvent<
        C extends EventClass<any>,
        ET extends (C extends EventClass<infer zET> ? zET : never),
        EK extends keyof ET,
        CK_T extends keyof Application.CmdData_Client,
    >(
        controller: C,
        controller_event: EK,
        app: Application | ((data: ET[EK]) => AndAsyncObj<Application | null>),
        cmd: CK_T,
        sendDataFun: (data: ET[EK]) => AndAsyncObj<Application.CmdData_Client[CK_T]['param']>,
        eventDataFun: null | ((data: Application.CmdData_Client[CK_T]['back'], event: EventData<ET[EK], string>) => AndAsyncObj<ET[EK] | null>) = null
    ) {
        return controller.addEventHandler(controller_event, e => {
            return of(app)
                .pipe(concatMap(aFun => {
                    if (typeof aFun === 'function') return get_back_observable(aFun(e.data));
                    return of(aFun);
                }))
                .pipe(concatMap(application => {
                    if (!application) return of(null);
                    if (!application.isReady) return of(null);
                    return get_back_observable(sendDataFun(e.data))
                        .pipe(concatMap(param => application.send(cmd, param as any).pipe(tap(() => {
                            this._log.info('send event:' + cmd);
                        }))))
                }))
                .pipe(catchError(err => of(this._err(err))))
                .pipe(concatMap(back => {
                    if (back === null) return of(null);
                    if (back instanceof Error) return of(null);
                    if (!eventDataFun) return of(null);
                    return get_back_observable(eventDataFun(back, e))
                        .pipe(map(data => {
                            if (!data) return;
                            e.next(data);
                        }));
                }));
        });
    }

    private _apps: Application[] = [];

    getApp(id: string | AppAisle) {
        for (let app of this._apps) {
            if (typeof id === 'string') {
                if (app.id === id) return app;
            } else {
                if (app.aisle === id) return app;
            }
        }
        return null;
    }

    buildApp(options: Partial<Application.Options>) {
        const app: Application = this._newClass(Application, options, this);
        this.sendEvent(
            app, 'close', app, 'app_event_close',
            data => data,
            data => data,
        );
        this.sendEvent(
            app, 'destroy_all_window', app, 'app_event_destroy_all_window',
            data => ({ appid: app.id! }),
        );
        this._apps.push(app);
        return app;
    }

}